/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.gephi.statistics.spi;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.gephi.graph.api.DirectedGraph;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
import org.gephi.graph.api.Graph;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.Table;
import org.gephi.utils.longtask.spi.LongTask;
import org.gephi.utils.progress.ProgressTicket;
import org.openide.util.Exceptions;

/**
 *
 * @author lczgywzyy
 */
public class CSMetric implements Statistics, LongTask {

    private String report = "Success!!";

    private boolean isDirected;

    private boolean cancel = false;
    private ProgressTicket progressTicket;

    private final Properties props = System.getProperties();
    private final String separator = props.getProperty("file.separator");//.toLowerCase().startsWith("win"))? "\\":"/";
//    private final String tmpDir = props.getProperty("java.io.tmpdir");
    private final String userHome = props.getProperty("user.home");

    private final String Term_Info = userHome + separator + ".csmetrics" + separator + "term_info.txt";
    private final String Termid_List = userHome + separator + ".csmetrics" + separator + "termid_list.txt";
    private final String Co_Matrix = userHome + separator + ".csmetrics" + separator + "co_mat.txt";
    private final String Waf_Matrix = userHome + separator + ".csmetrics" + separator + "waf_mat.txt";
    private final String Affinity_Matrix = userHome + separator + ".csmetrics" + separator + "affinity_mat.txt";

    static {
        System.loadLibrary("waf");
    }

    private native int intMethod(int num);

    @Override
    public void execute(GraphModel graphModel) {
        isDirected = graphModel.isDirected();

        Graph graph = null;
        if (isDirected) {
            graph = graphModel.getDirectedGraphVisible();
        } else {
            graph = graphModel.getUndirectedGraphVisible();
        }
        execute(graph);

    }

    private void execute(Graph hgraph) {
        initializeAttributeColunms(hgraph.getModel());
        hgraph.readLock();
        Map<Integer, HashSet> outerSetMap = new HashMap<Integer, HashSet>();
        Map<Integer, HashSet> innerSetMap = new HashMap<Integer, HashSet>();
        HashSet<Node> startNodeList = new HashSet<Node>();
        HashSet<Node> endNodeList = new HashSet<Node>();
        HashMap<String, Integer> termid_map = new HashMap<String, Integer>();
        prepareTerminfoAndTermidlist(hgraph, startNodeList, endNodeList, termid_map, innerSetMap, outerSetMap);
//        calculateCoMatrix(Term_Info, Termid_List, Co_Matrix);
//        calculateWafMatrix(Co_Matrix, Term_Info, Waf_Matrix);
//        calculateAffinityMatrix(Waf_Matrix, Term_Info, Affinity_Matrix);
//        Map<Integer, Float> Waf_Values = calculateWafValues(Waf_Matrix, innerSetMap, outerSetMap);
//        saveCalculatedValues(hgraph, Waf_Values);
        hgraph.readUnlock();
    }

    @Override
    public String getReport() {
        return report;
    }

    @Override
    public boolean cancel() {
        cancel = true;
        return true;
    }

    @Override
    public void setProgressTicket(ProgressTicket pt) {
        this.progressTicket = progressTicket;
    }

    public void setDirected(boolean isDirected) {
        this.isDirected = isDirected;
    }

    public boolean isDirected() {
        return isDirected;
    }

    private void initializeAttributeColunms(GraphModel graphModel) {
        Table nodeTable = graphModel.getNodeTable();
        if (!nodeTable.hasColumn("CSMetrics")) {
//            nodeTable.addColumn("CSMetrics", "CSMetrics Centrality", Double.class, new Double(0));
            nodeTable.addColumn("CSMetrics", "CSMetrics Centrality", Float.class, new Float(0));
        }
    }

    /**
     * input: Aliase file; output: Term_Info, Termid_List
     */
    private void prepareTerminfoAndTermidlist(Graph hgraph, HashSet<Node> startNodeList, HashSet<Node> endNodeList, HashMap<String, Integer> termid_map, Map<Integer, HashSet> innerSetMap, Map<Integer, HashSet> outerSetMap) {
        LGraph lg = new LGraph(hgraph, 0);
//        LAF operation = new LAF(lg, 2729, 2719);
//        operation.getResult();
//        report = operation.getResultText();
        findAllStartNode(hgraph, startNodeList);
        findAllEndNode(hgraph, endNodeList);
        for (Node n1 : startNodeList) {
            for (Node n2 : endNodeList) {
                LAF operation = new LAF(lg, Integer.parseInt(("" + n1.getId()).trim()), Integer.parseInt(("" + n2.getId()).trim()));
                operation.getResult();
                report = operation.getResultText();
            }
        }
//        writeTermid_List(hgraph, Termid_LisLt, startNodeList, termid_map, innerSetMap, outerSetMap);
//        writeTerm_infor(Term_Info, termid_map);
//        report += "\n" + startNodeList.size() + "\n\n";
//        for (Node s : startNodeList) {
//            report += "" + s.getId() + "\n";
//        }
    }

    private void findAllStartNode(Graph hgraph, HashSet<Node> startNodeList) {
        for (Edge edge : hgraph.getEdges()) {
            EdgeIterable edgeIter = getPreEdgeIter(hgraph, edge.getSource(), true);
            String lineStr = edge.getSource().getId() + " " + edge.getTarget().getId();
            findPreEdges(hgraph, startNodeList, edgeIter, edge.getSource(), lineStr);
        }
    }

    private void findPreEdges(Graph hgraph, HashSet<Node> startNodeList, EdgeIterable edgeIter, Node targetNode, String lineStr) {
        if (edgeIter == null) {
            return;
        }
        if (!edgeIter.iterator().hasNext()) {
            startNodeList.add(targetNode);
            return;
        }
        for (Edge edge : edgeIter) {
            if (lineStr.contains(" " + edge.getSource().getId() + " ")) {
//                startNodeList.add(edge.getSource());
                continue;
            }
            String tmpLineStr = edge.getSource().getId() + " " + lineStr;
            EdgeIterable tmpEdgeIter = getPreEdgeIter(hgraph, edge.getSource(), true);
            findPreEdges(hgraph, startNodeList, tmpEdgeIter, edge.getSource(), tmpLineStr);
        }
    }

    private EdgeIterable getPreEdgeIter(Graph hgraph, Node v, boolean directed) {
        EdgeIterable edgeIter = null;
        if (directed) {
            edgeIter = ((DirectedGraph) hgraph).getInEdges(v);
        } else {
            edgeIter = hgraph.getEdges(v);
        }
        return edgeIter;
    }

    private void findAllEndNode(Graph hgraph, HashSet<Node> endNodeList) {
        for (Edge edge : hgraph.getEdges()) {
            EdgeIterable edgeIter = getPostEdgeIter(hgraph, edge.getTarget(), true);
            String lineStr = edge.getSource().getId() + " " + edge.getTarget().getId();
            findPostEdges(hgraph, endNodeList, edgeIter, edge.getTarget(), lineStr);
        }
    }

    private void findPostEdges(Graph hgraph, HashSet<Node> endNodeList, EdgeIterable edgeIter, Node targetNode, String lineStr) {
        if (edgeIter == null) {
            return;
        }
        if (!edgeIter.iterator().hasNext()) {
            endNodeList.add(targetNode);
            return;
        }
        for (Edge edge : edgeIter) {
            if (lineStr.contains(" " + edge.getTarget().getId() + " ")) {
                continue;
            }
            String tmpLineStr = lineStr + " " + edge.getTarget().getId();
            EdgeIterable tmpEdgeIter = getPostEdgeIter(hgraph, edge.getTarget(), true);
            findPostEdges(hgraph, endNodeList, tmpEdgeIter, edge.getTarget(), tmpLineStr);
        }
    }

    private EdgeIterable getPostEdgeIter(Graph hgraph, Node v, boolean directed) {
        EdgeIterable edgeIter = null;
        if (directed) {
            edgeIter = ((DirectedGraph) hgraph).getOutEdges(v);
        } else {
            edgeIter = hgraph.getEdges(v);
        }
        return edgeIter;
    }

    private void writeTermid_List(Graph hgraph, String filePath, HashSet<Node> startNodeList, HashMap<String, Integer> termid_map, Map<Integer, HashSet> innerSetMap, Map<Integer, HashSet> outerSetMap) {
        BufferedWriter bufWriter = null;
        try {
            bufWriter = new BufferedWriter(new FileWriter(filePath));
//            for (Edge edge : hgraph.getEdges()) {
//                Node sourceNode = edge.getSource();
//                Node targetNode = edge.getTarget();
//                String lineStr = " " + sourceNode.getId() + " " + targetNode.getId();
//                HashSet innerSet = innerSetMap.get(Integer.parseInt(("" + targetNode.getId()).trim()));
//                if (innerSet == null) {
//                    innerSet = new HashSet();
//                    innerSetMap.put(Integer.parseInt(("" + targetNode.getId()).trim()), innerSet);
//                }
//                innerSet.add(lineStr);
//                HashSet outerSet = outerSetMap.get(Integer.parseInt(("" + sourceNode.getId()).trim()));
//                if (outerSet == null) {
//                    outerSet = new HashSet();
//                    outerSetMap.put(Integer.parseInt(("" + sourceNode.getId()).trim()), outerSet);
//                }
//                outerSet.add(lineStr);
//
//                bufWriter.write(lineStr + " -1" + "\n");
//                EdgeIterable edgeIter = getEdgeIter(hgraph, targetNode, true);
//                writeAllEdges(bufWriter, hgraph, edgeIter, lineStr, innerSetMap, outerSetMap);
//            }
            for (Node node : startNodeList) {
                String lineStr = " " + node.getId();
                EdgeIterable edgeIter = getEdgeIter(hgraph, node, true);
                writeAllEdges(bufWriter, hgraph, edgeIter, termid_map, lineStr, innerSetMap, outerSetMap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void writeAllEdges(BufferedWriter bufWriter, Graph hgraph, EdgeIterable edgeIter, HashMap<String, Integer> termid_map, String lineStr, Map<Integer, HashSet> innerSetMap, Map<Integer, HashSet> outerSetMap) throws IOException {
        if (bufWriter == null) {
            return;
        }
        if (edgeIter == null || !edgeIter.iterator().hasNext()) {
            addTermIntoMaps(hgraph, lineStr, termid_map, innerSetMap, outerSetMap);
            bufWriter.write(lineStr + " -1" + "\n");
            return;
        }
        for (Edge edge : edgeIter) {
            Node targetNode = edge.getTarget();
            if (lineStr.contains(" " + targetNode.getId() + " ")) {
                String tmpStr = lineStr + " " + targetNode.getId();
                addTermIntoMaps(hgraph, tmpStr, termid_map, innerSetMap, outerSetMap);
                bufWriter.write(tmpStr + " -1" + "\n");
                continue;
            }
            String tmpLineStr = lineStr + " " + targetNode.getId();
//            HashSet innerSet = innerSetMap.get(Integer.parseInt(("" + targetNode.getId()).trim()));
//            if (innerSet == null) {
//                innerSet = new HashSet();
//                innerSetMap.put(Integer.parseInt(("" + targetNode.getId()).trim()), innerSet);
//            }
//            innerSet.add(tmpLineStr);
//            String sourceNodeID = (tmpLineStr.trim().contains(" ")) ? tmpLineStr.trim().split(" ")[0] : "-1";
//            HashSet outerSet = outerSetMap.get(Integer.parseInt(sourceNodeID.trim()));
//            if (outerSet == null) {
//                outerSet = new HashSet();
//                outerSetMap.put(Integer.parseInt(sourceNodeID.trim()), outerSet);
//            }
//            outerSet.add(tmpLineStr);
//            bufWriter.write(tmpLineStr + " -1" + "\n");
            EdgeIterable tmpEdgeIter = getEdgeIter(hgraph, targetNode, true);
            writeAllEdges(bufWriter, hgraph, tmpEdgeIter, termid_map, tmpLineStr, innerSetMap, outerSetMap);
        }
    }

    private EdgeIterable getEdgeIter(Graph hgraph, Node v, boolean directed) {
        EdgeIterable edgeIter = null;
        if (directed) {
            edgeIter = ((DirectedGraph) hgraph).getOutEdges(v);
        } else {
            edgeIter = hgraph.getEdges(v);
        }
        return edgeIter;
    }

    private void addTermIntoMaps(Graph hgraph, String targetStrings, HashMap<String, Integer> termid_map, Map<Integer, HashSet> innerSetMap, Map<Integer, HashSet> outerSetMap) {
        if (targetStrings != null && !"".equals(targetStrings) && targetStrings.contains(" ")) {
            String trimStr = targetStrings.trim();
            for (String str : trimStr.split(" ")) {
                String tmpStr = str + " " + hgraph.getNode(str).getLabel();
                termid_map.put(tmpStr, (termid_map.get(tmpStr) == null) ? 1 : termid_map.get(tmpStr) + 1);

                HashSet innerSet = innerSetMap.get(Integer.parseInt(("" + str).trim()));
                if (innerSet == null) {
                    innerSet = new HashSet();
                    innerSetMap.put(Integer.parseInt(("" + str).trim()), innerSet);
                }
                String tmpInner = trimStr.substring(0, trimStr.indexOf(str)).trim();
                if (tmpInner != null && !"".equals(tmpInner)) {
                    innerSet.add(tmpInner);
                }

                HashSet outerSet = outerSetMap.get(Integer.parseInt(("" + str).trim()));
                if (outerSet == null) {
                    outerSet = new HashSet();
                    outerSetMap.put(Integer.parseInt(("" + str).trim()), outerSet);
                }
                String tmpOuter = trimStr.substring(trimStr.indexOf(str) + str.length()).trim();
                if (tmpOuter != null && !"".equals(tmpOuter)) {
                    outerSet.add(tmpOuter);
                }
            }
        }
    }

    @SuppressWarnings("rawtypes")
    private void writeTerm_infor(String filePath, HashMap<String, Integer> termid_map) {
        BufferedWriter bufWriter = null;
        try {
            File f = new File(filePath).getParentFile();
            if (!f.exists()) {
                f.mkdirs();
            }
            bufWriter = new BufferedWriter(new FileWriter(filePath));
//            Map<String, Integer> termid_map = new HashMap<String, Integer>();
//            for (Edge edge : hgraph.getEdges()) {
//                String keyS = "" + edge.getSource().getId() + "\t" + edge.getSource().getLabel();
//                String keyT = "" + edge.getTarget().getId() + "\t" + edge.getTarget().getLabel();
//                termid_map.put(keyS, (termid_map.get(keyS) == null) ? 1 : (termid_map.get(keyS) + 1));
//                termid_map.put(keyT, (termid_map.get(keyT) == null) ? 1 : (termid_map.get(keyT) + 1));
//            }
            bufWriter.write("termid_size=" + termid_map.size() + "\n");
            bufWriter.write("<term_freq>	<termid>	<term>" + "\n");
            Iterator iter = termid_map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                String key = (String) entry.getKey();
                int val = (int) entry.getValue();
                bufWriter.write(val + "\t" + key.replace(" ", "\t") + "\n");
            }
            bufWriter.close();
        } catch (IOException e) {
            try {
                bufWriter.close();
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }

    /**
     * input: Term_Info, Termid_List; output: Co_Matrix
     */
    private native int calculateCoMatrix(String mTermInfo, String mTermidList, String mCoMatrix);

    /**
     * input: Term_Info, Co_Matrix; output: Waf_Matrix
     */
    private native int calculateWafMatrix(String mCoMatrix, String mTermInfo, String mWafMatrix);

    /**
     * input: Term_Info, Co_Matrix; output: Waf_Matrix
     */
    private native int calculateAffinityMatrix(String mWafMatrix, String mTermInfo, String mAffinityMatrix);

    @SuppressWarnings("rawtypes")
    private Map<Integer, Float> calculateWafValues(String mWafMatrix, Map<Integer, HashSet> innerSetMap, Map<Integer, HashSet> outerSetMap) {
        Map<Integer, Float> returnMap = new HashMap<Integer, Float>();
        Map<Integer, ArrayList<Float>> outerMap = new HashMap<Integer, ArrayList<Float>>();
        Map<Integer, ArrayList<Float>> innerMap = new HashMap<Integer, ArrayList<Float>>();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(mWafMatrix)));
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("row_size") && !line.startsWith("<row_index>") && line.contains("->")) {
                    line = line.trim();
                    int outerKey = Integer.parseInt(line.split("->")[0].trim());
                    if (outerMap.get(outerKey) == null || outerMap.get(outerKey).isEmpty()) {
                        ArrayList<Float> tmplist = new ArrayList<Float>();
                        outerMap.put(outerKey, tmplist);
                    }
                    if (returnMap.get(outerKey) == null) {
                        Float tmp = 0.0f;
                        returnMap.put(outerKey, tmp);
                    }
                    String tmpStr = line.split("->")[1].trim();
                    while (tmpStr.contains("(") && tmpStr.contains(":") && tmpStr.contains(")")) {
                        int innerKey = Integer.parseInt(tmpStr.substring(tmpStr.indexOf("(") + 1, tmpStr.indexOf(":")).trim());
                        if (innerMap.get(innerKey) == null || innerMap.get(innerKey).isEmpty()) {
                            ArrayList<Float> tmplist = new ArrayList<Float>();
                            innerMap.put(innerKey, tmplist);
                        }
                        if (returnMap.get(innerKey) == null) {
                            Float tmp = 0.0f;
                            returnMap.put(innerKey, tmp);
                        }
                        float value = Float
                                .parseFloat(tmpStr.substring(tmpStr.indexOf(":") + 1, tmpStr.indexOf(")")).trim());
                        outerMap.get(outerKey).add(value);
                        innerMap.get(innerKey).add(value);
                        tmpStr = tmpStr.substring(tmpStr.indexOf(")") + 1);
                    }
                }
            }
        } catch (FileNotFoundException fnfe) {
            fnfe.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Iterator iter = returnMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            int key = (int) entry.getKey();
            ArrayList<Float> outerValueList = outerMap.get(key);
            float outerValue = 0.0f;
            int outerValueSize = 0;
            if (outerValueList != null && !outerValueList.isEmpty()) {
                for (float v : outerValueList) {
                    outerValue += v;
                }
//                outerValueSize = outerValueList.size();
                outerValueSize = outerSetMap.get(key).size();
            }
            ArrayList<Float> innerValueList = innerMap.get(key);
            float innerValue = 0.0f;
            int innerValueSize = 0;
            if (innerValueList != null && !innerValueList.isEmpty()) {
                for (float v : innerValueList) {
                    innerValue += v;
                }
//                innerValueSize = innerValueList.size();
                innerValueSize = innerSetMap.get(key).size();
            }

//            float outerValeAverage = (outerValueSize != 0) ? (outerValue / outerValueSize) : 0;
//            float innerValeAverage = (innerValueSize != 0) ? (innerValue / innerValueSize) : 0;
            float outerValeAverage = (outerValueSize != 0) ? (outerValue) : 0;
            float innerValeAverage = (innerValueSize != 0) ? (innerValue) : 0;

            float value = (outerValeAverage >= innerValeAverage) ? outerValeAverage : innerValeAverage;
            returnMap.put(key, value);
        }
        return returnMap;
    }

    private void saveCalculatedValues(Graph hgraph, Map<Integer, Float> Waf_Values) {
//        Iterator iter = Waf_Values.entrySet().iterator();
//        while (iter.hasNext()) {
//            Map.Entry entry = (Map.Entry) iter.next();
//            int key = (int) entry.getKey();
//            float value = (float) entry.getValue();
//            Node s = hgraph.getNode(key);
//            s.setAttribute("CSMetrics", value);
//        }
        for (Node s : hgraph.getNodes()) {
//            s.setAttribute("CSMetrics", intMethod(2) * (double) 1.0);
            if (Waf_Values.get(Integer.parseInt((String) s.getId())) != null) {
                s.setAttribute("CSMetrics", Waf_Values.get(Integer.parseInt((String) s.getId())));
            }

        }
    }

}
